/***************************************************************************
 *   Copyright (C) 2010 by Oleg Goncharov  *
 *   $EMAIL$                           *                          
 *                                                                         *
 *   This file is part of ChessVision.                                     *
 *                                                                         *
 *   ChessVision is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include <iostream>
#include <opencv/cv.h>
#include <opencv/highgui.h>

#include "define.h"
#include "cfeaturefigure.h"
#include "error/cerror.h"

namespace Chess {

CFigureFeature::CFigureFeature(const std::string& conf_str, ClassModel _model, int history_length) : 
	config(conf_str),
	model(_model) 
{
	this->history_length = history_length;
	cols = 1;
	for(int i = 0; i < config.size(); i++) if (config[i] == ' ') cols++;
	
	data.create(64 + model*history_length, cols + 1);
	samples = data.colRange(0, cols);
	classes = data.colRange(cols, cols + 1);
	
	samples_idx.create(64 + model*history_length, 1);
	samples_idx.rowRange(0, 64) = 255;
	samples_idx.rowRange(64, 64 + model*history_length).setTo(0);
}

void CFigureFeature::Prepare(const cv::Mat& img, const CBoardCells& cells) {
	cv::split(img, sources + IMG_B);
	cv::cvtColor(img, sources[GREY], CV_BGR2GRAY);
	sources[GRAD].create(sources[GREY].size(), CV_16UC1);
	cv::createDerivFilter(CV_8UC1, CV_16UC1, 1, 1, 5)->apply(sources[GREY], sources[GRAD]);
	
	int half_h = cells.Get_h()/2;
	for(int i = 0; i < 10; i++) {
		masks[i].create(cells.Get_h(), cells.Get_h(), CV_8UC1);
		masks[i] = cv::Scalar(0);
		cv::circle(masks[i], cv::Point(half_h, half_h), (i+1)*half_h/10, cv::Scalar(255), -1);
	}
}

void CFigureFeature::Release() {
	for(int i = 0; i < NSOURCES; i++) sources[i].release();
	for(int i = 0; i < 10; i++) masks[i].release();
}


void CFigureFeature::CalcFeatures(const CBoardCells& cells, int ci, int cj, cv::Mat_<float>& data, int row) {
	int state = 0, n = 0;
	cv::Scalar tmp1, tmp2;
	int col = 0, mask;
	Operations op = AVG;
	Sources source = IMG_B;
	char c;
	double result, tmp;
	cv::Mat cell;
	
	while (n < config.size()) {
		c = config[n++];
		
		switch (state) {
		case 0: 
			// выбираем изображение источник
			state = 1;
			switch (c) {
			case 'R': source = IMG_R;  break; // Red
			case 'G': source = IMG_G;  break; // Green
			case 'B': source = IMG_B;  break; // Blue
			case 'M': source = GREY;  break; // Monocrome (grey)
			case 'g': source = GRAD;  break; // gradient
			default : state = 4; break; // ошибка
			}
			cell = sources[source](cells.CellRect(ci, cj));
			break;
		
		case 1:
			// выбирем размер маски
			state = 2;
			if ('1' <= c && c <= '9') mask = c - '1';
			else if (c == '0') mask = 9;
			else state = 4; // ошибка
			break;
			
		case 2: 
			// выбор операции
			// выполнение операции
			state = 3;
			switch (c) {
			case 'A': 
				op = AVG;
				result = cv::mean(cell, masks[mask])[0];
				break;
			case 'D': 
				op = SDV;
				cv::meanStdDev(cell, tmp1, tmp2, masks[mask]);
				result = tmp2[0];
				break;
			case 'M':
				op = MAX;
				cv::minMaxLoc(cell, &tmp, &result, 0 , 0, masks[mask]);
				break;
			 case 'm':
				op = MIN;
			 	cv::minMaxLoc(cell, &result, &tmp, 0, 0, masks[mask]);
				break;
			default: state = 4; break;
			}
			if (state == 3) data(row, col++) = result;
			break;
		
		case 3:
			//конец группы
			if (c == ' ') state = 0;
			else state = 4;
			break;
		
		case 4:
			// ошибка
			throw Errors::CError("class CFigureFeature: incorect config string\n");
		}	
	}
	if (state != 3) throw Errors::CError("class CFigureFeature: incorect copnfig string\n");
}
	
void CFigureFeature::CalcAllFeatures(const cv::Mat& img, const CBoardCells& cells) {
	int row = 0;
	Prepare(img, cells);
	//std::cout << "==========================================" << std::endl;
	for(int j = 0; j < 8; j++) 
		for(int i = 0; i < 8; i++) {
			CalcFeatures(cells, i, j, samples, row);
			//std::cout << "Row " << row << "(";
			//for(int p = 0; p < cols; p++) 
			//	std::cout << data(row, p) << " ";
			//std::cout << ")" << std::endl;
			row++;
		}
	Release();
}

void CFigureFeature::SetClasses(const CPosition& pos) {
	switch (model) {
	case SIMPLE:
		for(int i = 0; i < 64; i++) 
			if (pos.IsWhite(i)) classes(i, 0) = 10.0;
			else if (pos.IsBlack(i)) classes(i, 0) = -10.0;
			else classes(i, 0) = 0.0;
		break;
	case MEDIUM:
		for(int i = 0; i < 64; i++) 
			if (pos.IsWhite(i)) classes(i, 0) = 10.0;
			else if (pos.IsBlack(i)) classes(i, 0) = -10.0;
			else classes(i, 0) = i % 2;
			break;
	case COMPLEX:
		for(int i = 0; i < 64; i++) 
			if (pos.IsWhite(i)) classes(i, 0) = 10 + i % 2;
			else if (pos.IsBlack(i)) classes(i, 0) = -10 + i % 2;
			else classes(i, 0) = i % 2;
		break;
	}
}

void CFigureFeature::InitHistoryMap() {
	int index = 64;
	for(int i = 0; i < 64; i++)
		if (history_idx.count(classes(i, 0)) == 0) {
			history_idx[classes(i, 0)] = HistoryBlock(index, 0);
			index += history_length;
		}
	if (history_idx.size() != model) throw Errors::CError("CFigureFeature wrong class model."); 
}
	
void CFigureFeature::UpdateHistory() {
 	/*for(int r = 0; r < samples.rows; r++) {
 		std::cout << "Row " << r << "(";
 		for(int p = 0; p < cols; p++) std::cout << samples(r, p) << " ";
 		std::cout << ") -> " << classes(r, 0) << " -- " << int(samples_idx(r, 0)) << std::endl;
 	}*/
	
	if (history_length == 0) return;
	float cl;
	HistoryBlock * range;
	
	if (history_idx.empty()) InitHistoryMap();
	
	for(int i = 0; i < 64; i++) {
		 range = &history_idx[classes(i, 0)];
		 if ( ++range->idx_in_block >= history_length) range->idx_in_block = 0;
		 cv::Mat row = data.row(range->block_offset + range->idx_in_block);
		 data.row(i).copyTo(row);
		 samples_idx(range->block_offset + range->idx_in_block, 0) = 255;
	}
}

double CFigureFeature::InitialPositionAngle(const cv::Mat& img, const CBoardCells& cells) {
	std::string old_config = config;
	int old_cols = cols;
	config = "M7A"; cols = 1;
	CalcAllFeatures(img, cells);
	
	float tone = 0;
	float angle;
	for(int i = 2; i < 6; i++)
		for(int j = 2; j < 6; j++)
			if ( (i+j) % 2) tone += data(8*j + i, 0); //black square
			else tone -= data(8*j + i, 0);
	
	if (tone > 0) { // правильное положение клеток
		tone = 0;
		for(int i = 0; i < 16; i++) tone += data(i, 0) - data(63 - i, 0); 
		if (tone < 0) angle = PI; // неправильное положение фигур
		else angle = 0;
	}
	else { // неправильная ориентация клеток
		tone = 0;
		for(int i = 0; i < 64; i += 8) {
			tone += data(i + 6, 0) + data(i + 7, 0); 
			tone -= data(i, 0) + data(i + 1, 0);
		}
		if (tone < 0) angle = -PI/2; // правильное положение фигур
		else angle = PI/2;
	}
	
	config = old_config;
	cols = old_cols;
	return angle;
}

}


